Skip to content

Scope the Montana 2021 income tax rebate to its eligibility year - #8970

Open
PavelMakarchuk wants to merge 4 commits into
mainfrom
fix-mt-rebate-wiring
Open

Scope the Montana 2021 income tax rebate to its eligibility year#8970
PavelMakarchuk wants to merge 4 commits into
mainfrom
fix-mt-rebate-wiring

Conversation

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator

Summary

mt_income_tax_rebate returned the flat filing-status amount ($1,250/$2,500) for every MT filer in every year ≥ 2021, ignoring both the statutory liability cap — MCA 15-30-2191(2)(b): the rebate is the lesser of the amount or the taxpayer's income tax liability — and the rebate's 2021-only scope.

Montana state_income_tax results are unchanged (verified): the non-refundable credit application already floored the applied amount, and the credits list already restricts application to 2021. The defect was the variable's reported value — wrong for low-liability filers (e.g. a $20k joint couple: reported $2,500, statutory rebate $252.40) and nonzero in 2022+ — which mis-feeds any consumer of the variable (it produced phantom amounts in the TAXSIM-comparison srebate work, PolicyEngine/policyengine-taxsim#1070/#1068).

Changes

  • Formula caps at the smaller of the joint/separate-column pre-credit bases (the mt_files_separately election is post-credit and would create a computation cycle; the smaller base never exceeds the elected one).
  • rebate/amount.yaml zero-scoped from 2022-01-01.
  • Unit tests rewritten: the previous period-2023, no-income tests asserted the uncapped flat amounts (they encoded the bug); new tests cover above-cap single/joint, the capped low-liability case ($252.40), non-MT, and 2022 = 0.

Testing

Full Montana baseline suite: 536 passed, 0 failed. The TAXSIM-parity integration tests are unaffected (they input-override the rebate).

Fixes #8958.

🤖 Generated with Claude Code

The variable returned the flat filing-status amount (1,250/2,500) for every
MT filer in every year >= 2021, ignoring the statutory cap (lesser of the
amount or the taxpayer's income tax liability) and the rebate's 2021-only
scope. Montana income tax results are unchanged - the credit application
already floored the applied amount - but the variable's reported value was
wrong for low-liability filers and for 2022+ (it produced phantom amounts
in the TAXSIM-comparison srebate work).

Cap at the smaller of the joint and separate-column pre-credit bases (the
filing election itself is post-credit and would create a computation
cycle), zero-scope the amounts from 2022, and replace the period-2023
no-income unit tests that encoded the uncapped values.

Fixes #8958.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (724e795) to head (dd53545).
⚠️ Report is 29 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #8970   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         1           
  Lines           35        16   -19     
  Branches         2         0    -2     
=========================================
- Hits            35        16   -19     
Flag Coverage Δ
unittests 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MaxGhenis

Copy link
Copy Markdown
Contributor

Review: cap the Montana 2021 income tax rebate at liability

Verified the parameter values and cap logic against the enacted statute and reproduced the behavior on the PR branch. Verdict: approve. The change is correct, well-tested, and provably tax-neutral, with two minor accuracy notes and one nit.

Verified

  • Statute. MCA 15-30-2191 (HB 192, 2023) sets the rebate as the lesser of (i) the 2021 individual income tax liability reported on line 20 of the 2021 Form 2, or (ii) the filing-status amount — $1,250 (single / head of household / married filing separately) or $2,500 (joint / surviving spouse). The flat amounts in amount.yaml match.
  • Vectorization. The formula uses min_/max_/add with no array-level branching. The if p.capital_gains.in_effect in the base variables is a scalar parameter branch. Clean.
  • Tests pin the boundary. All 5 rebate tests pass. Independently reproduced the capped case: a $20,000 joint couple in 2021 has a joint pre-credit base of $252.40, and mt_income_tax_rebate returns $252.40 = min($2,500, $252.40). Above-cap single ($1,250) and joint ($2,500), non-MT ($0), and 2022 ($0) are all covered.
  • Tax-neutrality holds. Reproduced state_income_tax for the capped couple on both this branch and main: −33.81 in both cases (unchanged). The 2022 single case confirms the fix removes a phantom reported $1,250 (main reports 1,250; this branch reports 0) that was never actually applied.
  • 2022 zero-scoping is correct. mt_income_tax_rebate appears only in the 2021 non_refundable credit list, so the flat amount persisting into 2022+ was a reported-value artifact; zeroing it is right.

Minor: the cap base is line 18, but the statute specifies line 20

The statute caps at line 20 of the 2021 Form 2, which is "Tax after nonrefundable credits" (line 18 − line 19). This PR caps at mt_income_tax_before_non_refundable_credits, which is line 18 ("Tax liability before credits"). For a 2021 filer with other nonrefundable credits — in the model, the capital gains credit, which is applied before the rebate in the 2021 non_refundable list — the reported rebate can exceed the statutory line-20 cap by the amount of that credit.

This has no effect on state_income_tax (the credit application already floors the applied amount — verified), so it affects only the reported mt_income_tax_rebate value that downstream consumers see. The cycle-avoidance rationale for using pre-credit bases is sound with respect to the mt_files_separately election, but the capital gains credit is applied before the rebate and does not depend on it, so netting it out would not create a cycle. Consider capping at the pre-credit base net of the non-rebate nonrefundable credits, or documenting the line-18-vs-line-20 deviation in the comment.

Minor: citation precision

The "lesser of ... tax liability" cap is subsection (2)(a); (2)(b) is the flat filing-status amount. The PR title, changelog, and code comment attribute the cap to "(2)(b)"; more precisely, the "lesser of" is in the chapeau of (2), with (a) = liability and (b) = amount. The amount.yaml reference (citing the "(b)" amounts) is correct as written.

Nit

amount.yaml has a trailing space on the new 2022-01-01: 0 line under SEPARATE.

Out of scope (pre-existing, not introduced here)

For low-liability couples that elect separate filing, MT income tax can go slightly negative (for example −33.81 for the $20k couple) because the tax-unit-level rebate is projected onto persons in the separate-column allocation (mt_non_refundable_credits adds the TaxUnit rebate at Person level). This is identical on main and this branch, so it is not a regression — noting only for awareness.

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

Heads-up from taxsim triage (PolicyEngine/policyengine-taxsim#1078): this cap is one of three legs on that record, but it does not fix the per-person projection filed as #9012mt_non_refundable_credits (Person entity) adds the TaxUnit-level rebate once per spouse, so a joint couple still nets 2 × $2,500 = $5,000 with this PR applied. The two can land independently; noting it here so the cap isn't assumed to close out the #1078 record.

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

@MaxGhenis your review above concludes "Verdict: approve" but was posted as a comment, so the PR still shows as awaiting review — would you mind submitting it as a formal approval when you get a chance? Thanks!

@hua7450

hua7450 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Heads-up: #9019 merged (along with #9018), which changes the ground under this PR. git pull upstream main now conflicts on both mt_income_tax_rebate.py and mt_income_tax_rebate.yaml, and the conflict is semantic, not just textual: mt_income_tax_rebate is now a Person-entity variable that gives each spouse a $1,250 column share (the #9012 fix @MaxGhenis flagged as out-of-scope in his review above).

That interacts with this PR's two components differently:

The liability cap no longer belongs in the formula. Two reasons, one old and one new:

  1. (Max's point, sharpened by the DOR record) The statutory cap is line 20 of the 2021 Form 2 — tax after nonrefundable credits — and the DOR rebate report (May 2024, pp. 6, 10–11) confirms it was administered per filing configuration: MFJ couples got min($2,500, joint line 20), while married-filing-separately spouses were "treated as separate individuals," each capped at min($1,250, own column line 20). PolicyEngine's ordered credit application already reproduces exactly this: the rebate applies after the capital gains credit and each path floors at zero, so the effective rebate is line-20-capped per configuration in both the indiv and joint paths, with no formula change.
  2. (New, post-Apply MT 2021 income tax rebate once per return, not per spouse (#9012) #9019) Porting the cap into the now-Person-level formula would regress the joint path: mt_income_tax_before_refundable_credits_joint sums the person-level rebate via add(), so capping each spouse's $1,250 at their own column liability would deliver only $1,250 to a one-earner couple electing the joint column, where the statute (and DOR practice) grants min($2,500, joint line 20). The uncapped-per-person + per-path-floor design now on main is the correct architecture. Notably, mt_files_separately elects on final per-path tax including the rebate, which also captures the real-world amend-to-maximize behavior the DOR report describes (HB 816 had to cut off rebate amendments at May 1, 2023).

The 2022 zeroing in amount.yaml should definitely land. It's the genuinely behavioral piece: with only 2021-01-01 values, the flat amounts backfill forward and every MT filer in a 2022+ computation receives a phantom rebate credit (the existing integration tests carry mt_income_tax_rebate: 0 overrides precisely to suppress this). Zeroing from 2022 fixes it — and it also means the current period-2023 unit tests on main (single $1,250, joint [1_250, 1_250]) only pass because of the leak, so this PR's move of the unit tests to period 2021 (with the "No rebate in 2022" case) is needed regardless.

Suggested rework:

  • Keep main's (Apply MT 2021 income tax rebate once per return, not per spouse (#9012) #9019) formula; add a comment citing MCA 15-30-2191(2) noting the lesser-of-line-20 rule is enforced by the ordered per-path credit flooring.
  • Keep the amount.yaml zeroing (and fix the trailing space Max flagged).
  • Port this PR's realistic-income tests to period 2021 as person-level arrays, keep "No rebate in 2022," and re-express the capped case ($20k couple) as an mt_income_tax integration assertion — the floor in action — rather than a rebate-variable assertion.
  • Retitle/reword changelog around the year-scoping (and per Max, the lesser-of cite is § 2191(2) chapeau/(2)(a), not (2)(b)).

Auto-merge is disabled on this PR until the rework lands, since a formal approval of the current head would merge a stale TaxUnit formula over #9019.

…d liability cap

PRs #9018/#9019 landed after this branch was opened and moved
mt_income_tax_rebate to a Person-entity variable that splits the per-return
cap across each spouse's column. That is the correct architecture, so this
branch no longer carries its own TaxUnit formula:

- Take main's Person-entity formula unchanged. Porting the liability cap into
  it would under-pay a one-earner couple electing the joint column, which the
  statute and the DOR rebate report grant min($2,500, joint line 20).
- Document instead, on that formula, that MCA 15-30-2191(2)'s lesser-of rule
  is already enforced by the ordered non-refundable credit application, which
  floors each path at zero. Verified: a $20k joint couple has $252.40 of
  pre-credit liability and lands at exactly $0 before refundable credits.
- Keep amount.yaml's 2022 zeroing. This is the behavioural fix: without it the
  2021 amounts backdate forward and every Montana filer gets a phantom rebate.
- Rework the tests onto period 2021, add 2022/2023 year-scoping guards that
  assert mt_income_tax end to end, and re-express the low-liability case as
  the credit floor in action rather than a capped rebate value.
- Rename the changelog fragment to match the branch and rescope its wording.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PavelMakarchuk PavelMakarchuk changed the title Cap the Montana 2021 income tax rebate at liability (MCA 15-30-2191(2)(b)) and scope it to 2021 Scope the Montana 2021 income tax rebate to its eligibility year Aug 25, 2026
@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

Reworked in 0a6a95f along the lines @hua7450 laid out on 2026-07-17. Re-requesting review from @hua7450 and @MaxGhenis.

What changed

PRs #9018/#9019 landed after this branch was opened and moved mt_income_tax_rebate to a Person-entity variable that splits the per-return cap across each spouse's column. That is the correct architecture, so this branch no longer carries its own TaxUnit formula or the liability cap:

  1. Dropped the liability cap; took main's formula unchanged. Porting the cap into the person-level formula would under-pay a one-earner couple electing the joint column — capping each spouse's $1,250 against their own column yields $1,250, where the statute and the DOR rebate report (May 2024, pp. 6, 10–11) grant min($2,500, joint line 20).
  2. Documented why the cap is unnecessary, on main's formula, citing MCA 15-30-2191(2) — the chapeau/(2)(a), not (2)(b) as the old title and changelog said (thanks @MaxGhenis). The lesser-of-line-20 rule is already enforced by the ordered non-refundable credit application: mt_income_tax_before_refundable_credits_{indiv,joint} floors at zero.
    Verified on the branch — a $20k joint couple: pre-credit liability $252.40, pooled credits $2,500, before-refundable $0.00. (mt_income_tax reads −$33.81 for that household, entirely from the refundable Montana EITC, not the rebate.)
  3. Kept the 2022 zeroing in amount.yaml — the genuinely behavioural piece. Without it the 2021 amounts backdate forward and every Montana filer receives a phantom rebate credit. Also dropped the trailing space @MaxGhenis flagged.
  4. Reworked the tests. Active-year cases moved to period 2021 as person-level arrays; the low-liability case is now expressed as the credit floor in action rather than a capped rebate value; added 2022 and 2023 year-scoping guards that assert mt_income_tax end to end. The 2022 guard is discriminating: without the zeroing the leaked $1,250 would cut that filer's tax from $2,246.00 to $996.00.
    Note this is why main's existing period-2023 cases had to move — they only passed because of the backdating leak.
  5. Changelog renamed to match the branch (fix-mt-rebate-wiring.fixed.md) and rescoped to the year-scoping fix. PR title updated likewise.

Verification on the merged head: MT rebate tests 8 passed; full Montana suite 558 passed; partner contract suite 630 passed (no partner test references MT, so there is no partner-facing impact); test_parameter_files.py 7 passed; ruff format --check clean.

Still open, unchanged by this PR: the per-person projection tracked in #9012 is resolved on main by #9019, so the note I left on 2026-07-13 about a joint couple netting 2 × $2,500 no longer applies here. The branch was 908 commits behind and is now merged up to date.

@DTrim99

DTrim99 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Heads up @PavelMakarchuk — this has a sibling in the same subsystem: the Montana property tax rebate (mt_property_tax_rebate, up to $675) has an analogous mis-wiring. It's listed in credits/non_refundable.yaml (2022 entry), so it reduces income tax, and since mt_non_refundable_credits is person-level it gets projected onto every household member ($675 × 3 = $2,025 for a family of three), understating MT tax. Unlike the income tax rebate you're handling here, the property tax rebate is a separate direct-payment program (own DOR application, not on Form 2), so I removed it from the credit list rather than capping/scoping it.

Filed as #9347, PR up at #9348. No file overlap with this one (I don't touch rebate/amount.yaml or the rebate variable/tests; you don't touch non_refundable.yaml). Flagging so the MT-rebate treatment stays deliberately consistent across the two — happy to fold it in here instead if you'd prefer.

@DTrim99

DTrim99 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Program Review — PR #8970: Montana 2021 income tax rebate (year-scoping + liability cap)

PR head: 0a6a95f3770ef91a1404f62e9c351900198dda1b
Baseline / merge-base: 5117f076515d263299dfe3b70492c37bf5a7517d
CI: 33/33 green.

Overview

The PR has two advertised purposes:

  1. Scope the rebate to the 2021 tax year (add 2022-01-01: 0 to amount.yaml so the
    one-time 2021 amounts stop leaking into 2022+). Delivered and correct.
  2. Cap the rebate variable at the smaller of the filing-status amount and the 2021 line-20
    liability
    (per the PR body). Not delivered — the .py change is comment-only.

The delivered half is well-sourced and genuinely guarded by tests. The un-delivered half is
the variable-level cap the PR body advertises; net-income results are nonetheless correct
because the cap is enforced downstream. Details below.

Source Documents

  • Statute: MCA 15-30-2191 (Temporary), "Individual income tax rebate" — the rebate equals
    the lesser of (a) the taxpayer's 2021 individual income tax liability as properly
    reported on line 20 of the 2021 Montana return, or (b) a flat cap of $1,250
    (single / head of household / married-filing-separate, at (1)(b)(i)) / $2,500 (married
    filing jointly, at (1)(b)(ii)). Subsection (2) restates the ceiling: the department "may
    not issue a rebate … that exceeds the taxpayer's individual income tax liability as properly
    reported on line 20." One-time, 2021 tax year only; issued by December 31, 2023; statute
    terminates December 31, 2025.
    • Live-URL termination (important): the cited href
      (archive.legmt.gov/.../0150-0300-0210-0910.html) now 301-redirects to mca.legmt.gov
      and renders only "15-30-2191. Terminated. Sec. 9, Ch. 44, L. 2023, sec. 10, Ch. 764, L.
      2023."
      A 2023 enactment reused the section number, so the live page no longer shows
      the 2021 rebate text or the $1,250/$2,500 amounts.
  • DOR report: MT DOR Income Tax and Property Tax Rebate Report, May 2024. Confirms both
    caps in plain English and that a joint couple receives min($2,500, joint line-20 liability) — the whole-return line 20 governs, not a per-spouse column ("whichever was
    lower"; "If a taxpayer's line 20 was less than $1,250, that amount was the rebate").
    Enacting bills: HB 192 (Ch. 44, L. 2023) + HB 816.

Branch Status

The branch is 32 commits behind main. This is noted for context only; it is not raised
as a review finding. CI is green on the current head.


Critical

C1 — Cited statute URL no longer corroborates the parameter values

Files: parameters/gov/states/mt/tax/income/credits/rebate/amount.yaml:8;
variables/gov/states/mt/tax/income/credits/rebate/mt_income_tax_rebate.py:10

Both the parameter and the variable cite the same href, which now resolves (verified live) to
a "Terminated" section because a 2023 enactment reused section number 15-30-2191. Clicking
the link does not display the $1,250/$2,500 amounts or any (1)(b)/(2) subsection — it fails
the core corroboration test ("when someone clicks the link, they should see the value"). A
reference that no longer corroborates the value provides false confidence.

Fix (choose one value-containing source): cite HB 192 (2023 Regular Session, Ch. 44,
Laws of 2023)
, or a Wayback Machine snapshot of MCA 15-30-2191 captured in 2023
(pre-termination), or (secondary) the Montana DOR "2021 Income Tax Rebate" guidance page. Apply
the same fix to both the parameter reference and the variable's bare-URL reference.

C2 — Subsection citation is imprecise

File: amount.yaml:7 — cited as "…Section 15-30-2191 (b)".

The bare "(b)" drops the (1) and the (i)/(ii) that actually pin the dollar figures: the amounts
live at 15-30-2191(1)(b)(i) ($1,250 single/HoH/separate) and (1)(b)(ii) ($2,500 joint),
and the standalone line-20 ceiling is at (2) — not cited at all. Since the implementation
relies on that ceiling (enforced downstream), the reference should name (1)(b)(i)–(ii) and (2).

Recommended title: "Montana Code Annotated (2023), 15-30-2191(1)(b)(i)–(ii) — rebate amounts
($1,250 single/HoH/separate; $2,500 joint); (2) — rebate capped at line 20 income tax
liability" (pointing at a value-containing source per C1).

Verification caveat: the live section is confirmed "Terminated" by direct fetch, but the full
2023 statute text could not be independently re-opened (Justia/FindLaw/Casetext blocked; HB 192
PDF path 404'd). The (1)(b)(i)/(ii) and (2) mapping is corroborated by the DOR report and the
Wayback snapshot used in the source audit; a maintainer should confirm exact subsection numbers
against the Wayback snapshot or the HB 192 enrolled text before finalizing the corrected cite.


Should Address

S1 — PR body claims a variable-level cap the code does not implement (central finding)

File: variables/gov/states/mt/tax/income/credits/rebate/mt_income_tax_rebate.py

The .py change is comment-only: the formula body is byte-for-byte identical to
baseline (5117f07). No min_() / min() cap was added. The variable still returns the flat
filing-status amount:

per_person_amount = where(
    filing_status == statuses.JOINT,
    p.amount["SEPARATE"],   # $1,250 per spouse for a JOINT return
    p.amount[filing_status],
)
return head_or_spouse * per_person_amount

Consequences:

  • mt_income_tax_rebate still reports the uncapped flat amount ($1,250 / $2,500) for
    low-liability filers. In the PR's own worked example (a ~$20k one-earner joint couple with a
    true pre-credit liability of $252.40) the variable reports [1_250, 1_250] while only $252.40
    of benefit survives. So the variable's reported value is not the statutory
    min(amount, line-20) for any filer whose liability is below the cap.
  • Net tax liability is nonetheless correct. The rebate enters as a member of the
    non-refundable credit list (non_refundable.yaml 2021 list →
    mt_non_refundable_credits.py addsmt_income_tax_before_refundable_credits_* applies
    max_(income − credits, 0)). That ordered floor is the only thing realizing the
    "lesser-of-liability" rule, and it is correct; mt_income_tax / state_income_tax are
    unchanged. mt_income_tax_rebate is referenced by no non-test formula other than that credit
    path (verified by grep).
  • The PR body is therefore inaccurate on two points: (a) it says "Formula caps at the
    smaller of the joint/separate-column pre-credit bases," which the formula does not do; and
    (b) its stated motivation — that the uncapped reported value mis-feeds a taxsim srebate
    output — is not resolved, because the reported value is unchanged. (No srebate /
    state_rebate consumer of this variable exists in the model today, so the un-capped value is
    currently latent/cosmetic, not an active net-income miscalculation — but it is a real
    deviation from the statute at the variable level and will mislead any future direct/srebate
    consumer.)

The added .py comment is candid ("that cap is NOT applied here"); the defect is that the PR
body overstates it
. Not applying the cap in the variable is a defensible design (capping
against a post-credit liability could create a computation cycle), but the PR body and code must
be reconciled.

Options (either resolves it): (1) correct the PR body to state the cap is emergent from
downstream credit ordering, not a formula-level cap, and drop/soften the srebate motivation;
or (2) apply min(per_person_amount, <person-level line-20 share>) in the formula so the
reported value is self-consistent with the statute. Also correct two PR-body-only citation
errors: "HB 3" → HB 192/816, and "(2)(b)" → (1)(b)/(2) (neither error appears in the
code or parameters — checked clean).

S2 — Liability-cap test coverage is partial

File: tests/.../mt_income_tax_rebate.yaml (post-PR, 6 cases)

Both sides of the cap are present (above-cap full amount in cases 1–2; below-cap partial
realization in case 3 at $252.40 < $2,500), so a gross regression to the downstream floor would
be caught. But the boundary is not probed, and every case asserts the uncapped rebate on
the variable — the cap only shows via downstream tax variables (these are integration
assertions, not unit assertions that the rebate equals min(amount, line 20)). Missing:

  • Exact-at-cap case (liability == $1,250 / == $2,500) and a cap ± 1 / ± epsilon pair —
    the single most valuable gap for "the crux"; a small near-threshold mis-set would go
    uncaught.
  • Single-filer low-liability case — only the JOINT low-liability path is tested; the indiv
    path uses a different variable (mt_income_tax_before_refundable_credits_indiv) and its own
    max_(…, 0) floor, currently unexercised.
  • SEPARATE (MFS-on-same-return) case — the formula's JOINT-vs-SEPARATE branch
    (p.amount["SEPARATE"]) is never exercised for an actual SEPARATE filer, which is exactly
    the configuration the new variable comment reasons about.
  • (Lower value) HEAD_OF_HOUSEHOLD and SURVIVING_SPOUSE 2022 zero-outs are unguarded.

Note: the year-scoping is genuinely guarded — cases 5 (2022) and 6 (2023) assert
mt_income_tax_rebate: [0] and would FAIL under the old amount.yaml (which backfilled $1,250
into 2022/2023). That half of the fix is regression-proof.


Suggestions

G1 — Trailing whitespace

amount.yaml:26 — the last value line 2022-01-01: 0 has a trailing space. make format /
prettier strips it; harmless (a carried-over nit, present one line up pre-PR) but clean before
merge.

G2 — SURVIVING_SPOUSE = $2,500 is inferred; add a clarifying comment

The statute and DOR report do not name a surviving-spouse / qualifying-widow(er) status.
Repo maps it to $2,500 (the JOINT maximum). This is defensible and the better choice
Montana derives filing status from the federal return, where a qualifying surviving spouse uses
the MFJ rate schedule and standard deduction, so line 20 is computed on the joint schedule and
the $2,500 cap is the internally consistent reading. But it is an inference, not stated in
either source; recommend a code comment noting the statute's silence and the joint-schedule
rationale.

G3 — Reconcile the 2022 test comment's "996.00" counterfactual

The 2022 case comment says "without the 2022 zeroing the leaked $1,250 would cut this to
996.00." But mt_income_tax_rebate is already de-listed from the 2022 non-refundable credit
list (non_refundable.yaml 2022 lists mt_capital_gain_credit + mt_property_tax_rebate,
not mt_income_tax_rebate), so the backfilled amount would only surface via a direct query
of the variable, not through mt_non_refundable_credits. The asserted 2_246.00 is likely
correct on its own; it is the causal story that looks wrong. Recommend the author confirm what
actually drives the phantom credit (direct-query vs credit list) so the comment/changelog is
accurate. (The amount.yaml 2022 zero-out is thus belt-and-suspenders for the direct-query
path — still worth keeping.)

G4 — Point the variable comment at its guarding test

mt_income_tax_rebate.py:17-27 — the comment asserts the rebate is "line-20-capped" but no cap
exists in this variable; it is realized by downstream credit ordering. A one-line pointer to the
guarding test would harden the implied cap against a future refactor of the credit chain that
could silently break it.


Source / Value Audit summary

All amounts match the statute — no mismatches. No value differs from the statutory maxima by
more than 0.3 for any enumerated status.

Filing status 2021-01-01 2022-01-01 Statute
SINGLE 1,250 0 (1)(b)(i) $1,250 — MATCH
HEAD_OF_HOUSEHOLD 1,250 0 (1)(b)(i) $1,250 — MATCH
JOINT 2,500 0 (1)(b)(ii) $2,500 — MATCH
SURVIVING_SPOUSE 2,500 0 not named; inferred from MFJ schedule — defensible (G2)
SEPARATE 1,250 0 (1)(b)(i) $1,250 — MATCH
  • One-time 2021 scope: correctly encoded via 2022-01-01: 0 (statute "(Temporary)",
    "one-time", "By December 31, 2023"; DOR "one-time only occurrence").
  • Liability cap: a real, twice-stated statutory requirement (15-30-2191(1)(a) and (2);
    DOR "whichever was lower"). Enforced correctly downstream for net income; not applied at
    the variable level (S1).
  • $252.40 low-liability figure: plausible and internally consistent (< the $2,500 cap;
    equals the joint pre-credit liability the test asserts); not independently recomputed to the
    cent — it functions as a regression anchor.

Validation Summary

Check Result
CI PASS (33/33)
Parameter values vs statute PASS (all 5 amounts + 5 zero-outs match)
One-time 2021 scoping (2022+ zero-out) PASS — delivered, tested (S2 note)
Variable-level liability cap (per PR body) FAIL — not delivered (comment-only); net income still correct (S1)
Net tax liability / state_income_tax correctness PASS (downstream max_(…,0) floor)
References corroborate values (live link) FAIL — terminated/reused section (C1)
Subsection citation precision FAIL — "(b)" vs (1)(b)(i)–(ii)/(2) (C2)
Test coverage — year-scoping PASS (regression-guarded)
Test coverage — liability cap boundary PARTIAL — no exact-boundary/single-low/SEPARATE (S2)
Period usage PASS (bare YYYY; 2021 active, 2022/2023 zero)
Changelog fragment PASS (fix-mt-rebate-wiring.fixed.md)
Formatting Minor — trailing whitespace (G1)
PR body accuracy FAIL — claims cap not in code; "HB 3"/"(2)(b)" errors (S1)

Review Severity: REQUEST_CHANGES

Two CRITICAL reference findings (dead-for-purpose statute link; imprecise subsection cite) are
both squarely in the "must-fix" category under the review rules (non-corroborating references
and incorrect section citations are CRITICAL). Neither blocks the delivered year-scoping fix,
which is correct and well-tested, but they must be corrected before merge. The central S1
finding — the PR body advertises a variable-level cap the code does not implement, and the
srebate motivation is unresolved — should be reconciled (correct the body or add the cap) so
the PR's description matches its behavior.

Next Steps

Run /fix-pr 8970 to:

  1. (C1) Swap the terminated statute href for a value-containing source (HB 192 session law or a
    2023 Wayback snapshot) in both amount.yaml and mt_income_tax_rebate.py.
  2. (C2) Correct the subsection citation to 15-30-2191(1)(b)(i)–(ii) and (2).
  3. (S1) Reconcile PR body ↔ code: either correct the body (cap is emergent downstream, drop the
    srebate claim) or apply min(per_person_amount, line-20 share) in the formula; fix the
    "HB 3" → HB 192/816 and "(2)(b)" → (1)(b)/(2) PR-body citations.
  4. (S2) Add exact-at-cap and cap±1 cases, a single-filer low-liability case, and a SEPARATE
    case.
  5. (G1–G4) Strip the trailing whitespace, add the SURVIVING_SPOUSE rationale comment, reconcile
    the 2022 "996.00" comment, and point the variable comment at its guarding test.

🤖 Generated with Claude Code /review-program

PavelMakarchuk and others added 2 commits August 27, 2026 01:12
…t the tax

The earlier comment claimed the leaked $1,250 would cut 2022 Montana tax
from $2,246 to $996. That is wrong, and verified so by counterfactual:
credits/non_refundable.yaml already drops mt_income_tax_rebate from the
list at 2022-01-01, so removing the amount.yaml sunset leaves
mt_non_refundable_credits at 0 and mt_income_tax at 2,246 unchanged - only
mt_income_tax_rebate itself moves, 0 -> 1,250.

The discriminating assertion is therefore the rebate variable alone; the
tax and credit-aggregate pins are unchanged controls, and the changelog
now scopes the fix to the reported value that mis-feeds consumers such as
the TAXSIM srebate comparison.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

Correction to my previous comment — one claim in it was wrong, pushed as dd53545.

I wrote that the 2022 guard was discriminating because "without the zeroing the leaked $1,250 would cut this filer's tax from $2,246.00 to $996.00." That is not true. I verified it by counterfactual — stripping the 2022-01-01: 0 entries and recomputing:

with the sunset without it
mt_income_tax_rebate 0 1,250
mt_non_refundable_credits 0 0
mt_income_tax 2,246.00 2,246.00

credits/non_refundable.yaml already drops mt_income_tax_rebate from the list at 2022-01-01, so the amount never reaches the credit aggregate in 2022 regardless. Montana tax is unaffected either way.

What this changes. The fix is still correct and worth landing, but its scope is narrower than I described, and it is the scope @PavelMakarchuk originally stated in the PR description: the defect is the variable's reported value, which mis-feeds consumers of it — the TAXSIM srebate comparison in PolicyEngine/policyengine-taxsim#1070/#1068 — not the tax outcome. It also means @hua7450's framing that "every MT filer in a 2022+ computation receives a phantom rebate credit" is a shade too strong: the phantom is in the variable, not in the credit that gets applied.

Applied: the misleading test comment is replaced with the accurate one (the rebate variable is the discriminating assertion, 0 vs 1,250; mt_income_tax and mt_non_refundable_credits are pinned as unchanged controls), and the changelog is rescoped to the reported value. Full Montana suite still 558 passed.

Everything else in my previous comment stands — in particular the verified $20k-couple decomposition ($252.40 pre-credit → $0.00 before refundable credits) showing the ordered credit application already enforces MCA 15-30-2191(2)'s lesser-of rule, which is why the liability cap is correctly dropped.

Heads-up @DTrim99: your #9348 edits the same credits/non_refundable.yaml (removing mt_property_tax_rebate from the 2022 list). No file overlap with this PR, and no semantic conflict that I can see — but the two touch adjacent parts of the same Montana credit wiring, so worth being aware of the ordering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Montana 2021 income tax rebate variable ignores the MCA 15-30-2191 liability cap and pays in all years >= 2021 (reporting; state_income_tax unaffected)

4 participants